package com.phiture.erp.finance.core.handler;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.map.MapUtil;
import com.phiture.erp.basic.api.pojo.resp.ErpMaterialInfoResp;
import com.phiture.erp.common.enums.ErpAuditStatus;
import com.phiture.erp.finance.common.enums.ErpFinanceStatementTypeEnum;
import com.phiture.erp.finance.core.pojo.dto.ErpGenerateFinanceStatementDTO;
import com.phiture.erp.finance.core.pojo.dto.ErpRemoveFinanceStatementDTO;
import com.phiture.erp.finance.dal.entity.ErpFinanceStatementDO;
import com.phiture.erp.finance.dal.entity.ErpFinanceStatementItemsDO;
import com.phiture.erp.sale.api.pojo.req.ErpSaleOutReq;
import com.phiture.erp.sale.api.pojo.req.ErpSaleReturnReq;
import com.phiture.erp.sale.api.pojo.resp.ErpSaleOutItemsResp;
import com.phiture.erp.sale.api.pojo.resp.ErpSaleOutResp;
import com.phiture.erp.sale.api.pojo.resp.ErpSaleReturnItemsResp;
import com.phiture.erp.sale.api.pojo.resp.ErpSaleReturnResp;
import com.phiture.erp.sale.api.service.ErpSaleOutApi;
import com.phiture.erp.sale.api.service.ErpSaleReturnApi;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author YH
 * @date 2025-06-12-8:31
 * 收款单
 */

@Slf4j
@Component
@RequiredArgsConstructor
public class ErpFinanceCustomerHandler extends ErpBaseFinanceStatementHandler implements ErpFinanceStatementHandler {
    private final ErpSaleOutApi erpSaleOutApi;
    private final ErpSaleReturnApi erpSaleReturnApi;

    @Override
    public boolean generate(ErpGenerateFinanceStatementDTO dto) {
        Pair<LocalDate, LocalDate> datePair = periodConvert(dto.getBillingPeriod());
        LocalDate startDate = datePair.getKey(), endDate = datePair.getValue();
        List<ErpSaleOutResp> saleOutList = getSaleOutList(startDate, endDate);
        List<ErpSaleReturnResp> saleReturnList = getSaleReturnList(startDate, endDate);
        if(CollUtil.isEmpty(saleOutList) && CollUtil.isEmpty(saleReturnList)){
            log.warn("[ErpFinanceReceiptHandler.generate] 生成客户对账单失败，销售出库单和销售退货单为空");
            return false;
        }
        List<ErpFinanceStatementDO> statementList = buildErpFinanceStatementDOList(dto, saleOutList, saleReturnList);
        Map<Long, javafx.util.Pair<String, Long>> saleOutMap = saleOutList.stream().collect(Collectors.toMap(ErpSaleOutResp::getSaleOutId, saleOut -> new javafx.util.Pair<>(saleOut.getSaleOutNo(), saleOut.getCustomerId())));
        Map<Long, javafx.util.Pair<String, Long>> saleReturnMap = saleReturnList.stream().collect(Collectors.toMap(ErpSaleReturnResp::getSaleReturnId, saleReturn -> new javafx.util.Pair<>(saleReturn.getSaleReturnNo(), saleReturn.getCustomerId())));
        List<ErpFinanceStatementItemsDO> statementItemList = buildErpFinanceStatementItemsDO(saleOutMap, saleReturnMap);
        return Boolean.TRUE.equals(transactionTemplate.execute(action -> {
            List<ErpFinanceStatementDO> saveStatementList = erpFinanceStatementService.batchSaveFinanceStatement(statementList);
            Map<Long, Long> statementMap = saveStatementList.stream().collect(Collectors.toMap(ErpFinanceStatementDO::getTransactionUserId, ErpFinanceStatementDO::getId));
            if (CollUtil.isNotEmpty(statementItemList)) {
                statementItemList.forEach(item -> item.setStatementId(statementMap.get(item.getTransactionUserId())));
                erpFinanceStatementService.batchSaveFinanceStatementItem(statementItemList);
            }
            erpSaleOutApi.backfillSaleOutAccountCheckFlag(saleOutMap.keySet(), Boolean.TRUE);
            erpSaleReturnApi.backfillSaleReturnAccountCheckFlag(saleReturnMap.keySet(), Boolean.TRUE);
            return true;
        }));
    }

    @Override
    public void remove(ErpRemoveFinanceStatementDTO dto) {
        long id = dto.getFinanceStatementId();
        checkFinanceStatementById(id);
        List<ErpFinanceStatementItemsDO> items = getItemsByFinanceStatementId(id);
        Set<String> noSet = new HashSet<>(items.size());
        List<Long> itemIdList = new ArrayList<>(items.size());
        items.forEach(item ->{
            noSet.add(item.getSourceNo());
            itemIdList.add(item.getId());
        });

        transactionTemplate.execute(action ->{
            erpSaleOutApi.updateSaleOutAccountCheckFlag(noSet, Boolean.FALSE);
            erpSaleReturnApi.updateSaleReturnAccountCheckFlag(noSet, Boolean.FALSE);
            removeFinanceStatementById(id);
            removeFinanceStatementItemByItemIds(itemIdList);
            return true;
        });
    }

    @Override
    public String getStrategyType() {
        return ErpFinanceStatementTypeEnum.CUSTOMER_RECEIPT.getVal();
    }

    private List<ErpSaleOutResp> getSaleOutList(LocalDate startDate, LocalDate endDate){
        ErpSaleOutReq req = new ErpSaleOutReq();
        req.setStatus(ErpAuditStatus.APPROVE.getStatus());
        req.setStartDate(startDate);
        req.setEndDate(endDate);
        return erpSaleOutApi.getErpSaleOutList(req);
    }

    private List<ErpSaleReturnResp> getSaleReturnList(LocalDate startDate, LocalDate endDate){
        ErpSaleReturnReq req = new ErpSaleReturnReq();
        req.setStatus(ErpAuditStatus.APPROVE.getStatus());
        req.setStartDate(startDate);
        req.setEndDate(endDate);
        return erpSaleReturnApi.getErpSaleReturnList(req);
    }

    private List<ErpSaleOutItemsResp> getSaleOutItemList(Map<Long, javafx.util.Pair<String, Long>> saleOutMap){
        return MapUtil.isEmpty(saleOutMap) ? Collections.emptyList() : erpSaleOutApi.getErpSaleOutItemList(saleOutMap);
    }

    private List<ErpSaleReturnItemsResp> getSaleReturnItemList(Map<Long, javafx.util.Pair<String, Long>> saleReturnMap){
        return MapUtil.isEmpty(saleReturnMap) ? Collections.emptyList() : erpSaleReturnApi.getErpSaleReturnItemList(saleReturnMap);
    }

    private List<ErpFinanceStatementDO> buildErpFinanceStatementDOList(ErpGenerateFinanceStatementDTO dto, List<ErpSaleOutResp> saleOutList, List<ErpSaleReturnResp> saleReturnList) {
        Map<Long, ErpFinanceStatementDO> financeStatementMap = new HashMap<>(saleOutList.size() + saleReturnList.size());
        if (CollUtil.isNotEmpty(saleOutList)) {
            saleOutList.forEach(
                    saleOut -> financeStatementMap.merge(
                            saleOut.getCustomerId(),
                            toErpFinanceStatementDO(saleOut.getCustomerId(), dto.getType(), dto.getBillingPeriod(), saleOut.getTotalPrice()),
                            (originalValue, newValue) -> originalValue.setTotalAmount(originalValue.getTotalAmount().add(newValue.getTotalAmount()))
                    )
            );
        }

        if (CollUtil.isNotEmpty(saleReturnList)) {
            saleReturnList.forEach(
                    saleReturn -> financeStatementMap.merge(
                            saleReturn.getCustomerId(),
                            toErpFinanceStatementDO(saleReturn.getCustomerId(), dto.getType(), dto.getBillingPeriod(), saleReturn.getTotalPrice().negate()),
                            (originalValue, newValue) -> originalValue.setTotalAmount(originalValue.getTotalAmount().add(newValue.getTotalAmount()))
                    )
            );
        }
        return MapUtil.isEmpty(financeStatementMap) ? Collections.emptyList() : new ArrayList<>(financeStatementMap.values());
    }

    private List<ErpFinanceStatementItemsDO> buildErpFinanceStatementItemsDO(Map<Long, javafx.util.Pair<String, Long>> saleOutMap, Map<Long, javafx.util.Pair<String, Long>> saleReturnMap){
        List<ErpSaleOutItemsResp> saleOutItemList = getSaleOutItemList(saleOutMap);
        List<ErpSaleReturnItemsResp> saleReturnItemList = getSaleReturnItemList(saleReturnMap);
        if(CollUtil.isEmpty(saleOutItemList) && CollUtil.isEmpty(saleReturnItemList)){
            return Collections.emptyList();
        }

        Set<Long> materialIds = Stream.concat(
                saleOutItemList.stream().map(ErpSaleOutItemsResp::getMaterialId),
                saleReturnItemList.stream().map(ErpSaleReturnItemsResp::getMaterialId)
        ).collect(Collectors.toSet());
        Map<Long, ErpMaterialInfoResp> materialMap = getMaterialMap(materialIds);

        List<ErpFinanceStatementItemsDO> statementItems = new ArrayList<>(saleOutMap.size() + saleReturnMap.size());
        if(CollUtil.isNotEmpty(saleOutItemList)){
            saleOutItemList.forEach(item -> statementItems.add(
                    toErpFinanceStatementItemsDO(
                            item.getSaleOutId(), item.getSaleOutNo(), item.getSaleOutItemId(), item.getCustomerId(),
                            item.getCount(), item.getMaterialPrice(), item.getTaxPercent(),
                            convertPrice(item.getTaxPrice()), convertPrice(item.getTotalPrice()), materialMap.get(item.getMaterialId())
                    )
            ));
        }

        if(CollUtil.isNotEmpty(saleReturnItemList)){
            saleReturnItemList.forEach(item -> statementItems.add(
                    toErpFinanceStatementItemsDO(
                            item.getSaleReturnId(), item.getSaleReturnNo(), item.getSaleReturnItemId(), item.getCustomerId(),
                            item.getCount().negate(), item.getMaterialPrice(), item.getTaxPercent(),
                            convertPrice(item.getTaxPrice()).negate(), convertPrice(item.getTotalPrice()).negate(), materialMap.get(item.getMaterialId())
                    )
            ));
        }
        return statementItems;
    }

}
